package Client.serviceCenter.balance.impl;

import Client.serviceCenter.balance.LoadBalance;

import java.util.*;

// 一致性哈希算法
public class ConsistencyHashBalance implements LoadBalance {

    // 虚拟节点个数
    private static final int VIRTUAL_NODE_NUM = 5;
    // 保存虚拟节点的hash值和对应的虚拟节点，key为hash值，value为虚拟节点名称
    private SortedMap<Integer, String> shards = new TreeMap<Integer, String>();
    // 真实节点列表
    private List<String> realNodes = new ArrayList<String>();
    // 模拟初始服务器
    private String[] serves = null;

    // 初始化负载均衡器，将真实的服务节点和对应的虚拟节点添加到哈希环中
    private void init(List<String> serviceList) {
        for (String server : serviceList) {
            realNodes.add(server);
            System.out.println("添加真实节点：" + server);
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                // 虚拟节点的名称为真实节点名称+"&&VN"+i，VN为虚拟节点的前缀
                String virtualNodeName = server + "&&VN" + i;
                // 计算每个虚拟节点的哈希值，并将虚拟节点添加到哈希环中
                int hash = getHash(virtualNodeName);
                // SortedMap根据key排序，所以虚拟节点的哈希值是按照顺时针排序的
                shards.put(hash, virtualNodeName);
                System.out.println("添加虚拟节点：" + virtualNodeName + "，虚拟节点的哈希值为：" + hash);
            }
        }
    }

    // 根据请求的node，在哈希环中查找距离请求的node最近的那个虚拟节点，返回该虚拟节点对应的真实节点
    public String getServer(String node, List<String> addressList) {
        // 首先调用init方法，初始化哈希环和虚拟节点
        init(addressList);
        int hash = getHash(node);
        Integer key = null;
        // 获取hash值大于请求的哈希值的所有虚拟节点
        SortedMap<Integer, String> map = shards.tailMap(hash);
        if (map.isEmpty()) {
            key = shards.lastKey();
        } else
            key = map.firstKey();
        String virtualNode = shards.get(key);
        return virtualNode.substring(0, virtualNode.indexOf("&&"));
    }

    // 模拟负载均衡，通过生成一个随机字符串(random)模拟请求，一致性哈希选择真实服务器
    @Override
    public String balance(List<String> addressList) {
        // 生成随机字符串(UUID)，作为node参数传递给getServer方法，获取服务器地址
        String random = UUID.randomUUID().toString();
        return getServer(random, addressList);
    }

    // 添加真实节点及其虚拟节点到哈希环中
    @Override
    public void addNode(String node) {
        if (!realNodes.contains(node)) {
            realNodes.add(node);
            System.out.println("添加真实节点：" + node);
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                String virtualNodeName = node + "&&VN" + i;
                int hash = getHash(virtualNodeName);
                shards.put(hash, virtualNodeName);
                System.out.println("添加虚拟节点：" + virtualNodeName + "，虚拟节点的哈希值为：" + hash);
            }
        }
    }

    // 删除真实节点及其虚拟节点
    @Override
    public void removeNode(String node) {
        if (realNodes.contains(node)) {
            realNodes.remove(node);
            System.out.println("删除真实节点：" + node);
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                String virtualNodeName = node + "&&VN" + i;
                int hash = getHash(virtualNodeName);
                shards.remove(hash);
                System.out.println("删除虚拟节点：" + virtualNodeName + "，虚拟节点的哈希值为：" + hash);
            }
        }
    }

    // FNV1_32_HASH算法，计算哈希值
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash ^ str.charAt(i)) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        // 如果算出来的值为负数则取其绝对值
        if (hash < 0) {
            hash = Math.abs(hash);
        }
        return hash;
    }
}
